# @Time : 2025-01-14 17:29
# @Author : Fioman 
# @Phone : 13149920693
"""
裁磨的Plc,三菱plc通信库,使用这个HslCommunication
"""
import threading
import time

import gevent
from HslCommunication import MelsecMcNet

from common.order_data_model import GrindOrder
from common.produce_logger import debug_produce
from personal_tool.data_result import DataResult


class GrindPlc():
    _lock = threading.Lock()
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            with cls._lock:
                cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self):
        if not hasattr(self, "_initFlag"):
            self.ip = "192.168.1.10"
            self.port = 5020
            self._initFlag = True
            debug_produce(f"in GrindPlc.__init__() plc初始化成功")
            self.plc = MelsecMcNet(self.ip, self.port)

    def connect(self) -> DataResult:
        result = DataResult()
        res = self.plc.ConnectServer()
        if res.IsSuccess:
            result.state = True
            return result
        else:
            result.info = res.Message
            return result

    def get_grind_plc_state(self) -> DataResult:
        """
        读取设备的状态,可能是运行状态或者是其他的状态.
        :return:
        """
        readPos = "D17152"
        return self.read_word(readPos)

    def get_finish_count(self) -> DataResult:
        """
        获取设备当前的生产数量
        """
        readPos = "D701"
        return self.read_word(readPos)

    def request_change_order(self):
        """
        请求下发工单
        """
        writePos = "D17150"
        writeVal = 1
        return self.write_word(writePos, writeVal)

    def clear_allow_change_order(self):
        """
        清除信号
        """
        writePos = "D17151"
        return self.write_word(writePos, 0)

    def get_allow_change_order(self):
        """
        获取是否允许下发工单的请求
        1:  下发成功
        2: 参数下发异常
        3: 机台有板中,不允许下发
        """
        readPos = "D17151"
        return self.read_word(readPos)

    def write_order_info_to_grind_plc(self, order: GrindOrder) -> DataResult:
        start = time.time()
        timeout = 3  # 设置3秒超时

        try:
            # 使用列表存储所有写入操作
            write_operations = [
                (lambda: self.write_ascii_string_to_plc("D17000", order.orderId,30), "工单号"),
                (lambda: self.write_ascii_string_to_plc("D17020", order.lotNum,12), "批号"),
                (lambda: self.write_double_word("D17042", order.lenY), "长度"),
                (lambda: self.write_double_word("D17044", order.widthX), "宽度"),
                (lambda: self.write_double_word("D17046", order.holeDisX), "孔距X"),
                (lambda: self.write_double_word("D17048", order.centerDisZ), "孔距Z"),
                (lambda: self.write_double_word("D17050", order.thickness), "板厚"),
                (lambda: self.write_double_word("D17052", order.thicknessSe), "板厚公差"),
                (lambda: self.write_word("D17054", order.isEdge), "是否磨边"),
                (lambda: self.write_word("D17055", order.isCircle), "是否圆角"),
                (lambda: self.write_word("D17056", order.planCount), "计划数量"),
                (lambda: self.write_word("D17058", order.isDirectionHole), "是否方向孔"),
                (lambda: self.write_double_word("D17059", order.disC0), "方向孔距C0"),
                (lambda: self.write_double_word("D17061", order.disF0), "方向孔距F0")
            ]

            # 执行每个写入操作
            for write_op, param_name in write_operations:
                if time.time() - start > timeout:
                    return DataResult(info=f"写入超时: 写入{param_name}时超过{timeout}秒")

                result = write_op()
                if not result.state:
                    return DataResult(info=f"写入{param_name}失败: {result.info}")

            elapsed = time.time() - start
            return DataResult(
                state=True,
                info=f"裁磨线工单下发成功,写入耗时: {elapsed:.2f} 秒"
            )
        except Exception as e:
            return DataResult(
                info=f"写入异常: {str(e)}, 耗时: {(time.time() - start):.2f} 秒"
            )

    def read_word(self, readPos) -> DataResult:
        """
        三菱Plc读取单字,只读取一个值.
        """
        opRes = self.plc.ReadInt16(readPos, 1)
        if opRes.IsSuccess:
            return DataResult(state=True, data=opRes.Content[0])
        else:
            return DataResult(info=opRes.Message)

    def write_word(self, writePos, writeVal) -> DataResult:
        opRes = self.plc.WriteInt16(writePos, writeVal)
        if opRes.IsSuccess:
            return DataResult(state=True)
        else:
            return DataResult(info=opRes.Message)

    def write_string(self, address, info, len):
        """
        写入字符串
        """
        opRes = self.plc.WriteUnicodeString(address, info, len)
        if opRes.IsSuccess:
            return DataResult(state=True)
        else:
            return DataResult(info=opRes.Message)

    def write_double_word(self, address, writeVal):
        """
        三菱Plc写入双字
        """
        opRes = self.plc.WriteInt32(address, writeVal)
        if opRes.IsSuccess:
            return DataResult(state=True)
        else:
            return DataResult(info=opRes.Message)

    # 写入ascii字符串,strLen为字符串长度,就是如果字符串长度小于strLen,则补齐0x00,如果大于strLen,则截断
    def write_ascii_string_to_plc(self, address, value, strLen) -> DataResult:
        # 将字符串转为ASCII字节数组（1字符=1字节）
        ascii_bytes = bytes(value, encoding='ascii')
        # 如果字符串长度小于strLen,则补齐0x00,如果大于strLen,则截断
        if len(ascii_bytes) < strLen:
            ascii_bytes += b'\x00' * (strLen - len(ascii_bytes))
        elif len(ascii_bytes) > strLen:
            ascii_bytes = ascii_bytes[:strLen]  

        # 填充为偶数长度（若奇数长度，末尾补0x00）
        if len(ascii_bytes) % 2 != 0:
            ascii_bytes += b'\x00'

        # 将字节数组转为16位字数组（大端序）
        words = []
        for i in range(0, len(ascii_bytes), 2):
            high_byte = ascii_bytes[i]
            low_byte = ascii_bytes[i + 1] if (i + 1) < len(ascii_bytes) else 0x00
            word = (high_byte << 8) | low_byte  # 大端序：高位在前
            words.append(word)

        # 将整数列表转换为字节数组（关键修正）
        byte_buffer = bytearray()
        for word in words:
            # 将每个16位字拆分为高字节和低字节（大端序）
            byte_buffer.append((word >> 8) & 0xFF)  # 高字节
            byte_buffer.append(word & 0xFF)  # 低字节

        # 写入PLC寄存器（传入bytearray）
        self.plc.Write(address, byte_buffer)
        return DataResult(state=True)


if __name__ == '__main__':
    grindPlc = GrindPlc()
    result = grindPlc.connect()
    if result.state:
        print("连接成功")
    else:
        print("连接失败")

    # 0. 读取数量
    result = grindPlc.get_finish_count()
    if result.state:
        print(f"读取数量成功,读取的值为:{result.data}")
    else:
        print(f"读取数量失败:失败原因:{result.info}")

    # 1. 测试读取设备状态
    result = grindPlc.get_grind_plc_state()
    if result.state:
        print(f"读取plc设备状态成功,读取的状态为: {result.data}")
    else:
        print(f"读取plc设备状态失败,失败原因: {result.info}")

    # 测试下发工单
    orderId = "U61K001-0"
    lotNum = "041"
    lenY = int(23.57 * 25.4 * 1000)
    widthX = int(20.57 * 25.4 * 1000)
    holeDisX = int(12 * 25.4 * 1000)
    centerDisZ = int(16 * 25.4 * 1000)
    thickness = int(0.045 * 25.4 * 1000)
    thicknessSe = int(0.004 * 25.4 * 1000)
    isEdge = 1
    isCircle = 1
    planCount = 156
    isDirectionHole = 1
    disC0 = int(6 * 25.4 * 1000)
    disF0 = int(6 * 25.4 * 1000)

    testOrder = GrindOrder()
    testOrder.orderId = orderId
    testOrder.lotNum = lotNum
    testOrder.lenY = lenY
    testOrder.widthX = widthX
    testOrder.holeDisX = holeDisX
    testOrder.centerDisZ = centerDisZ
    testOrder.thickness = thickness
    testOrder.thicknessSe = thicknessSe
    testOrder.isEdge = isEdge
    testOrder.isCircle = isCircle
    testOrder.planCount = planCount
    testOrder.isDirectionHole = isDirectionHole
    testOrder.disC0 = disC0
    testOrder.disF0 = disF0
    result = grindPlc.write_order_info_to_grind_plc(testOrder)
    if not result.state:
        print(f"写入失败: {result.info}")
    else:
        print(f"写入成功,写入的信息：{testOrder}")

    # 2. 测试读取工单下发
    result = grindPlc.request_change_order()
    if not result.state:
        print(f"下发工单请求失败,失败原因:{result.info}")
    else:
        print(f"下发工单请求成功")

    # 3. 读取是否允许下发工单,在3秒内没有收到回复就报警
    startTime = time.time()
    while True:
        if time.time() - startTime > 3:
            print("请求工单下发失败")
            print("通信超时,超过了3秒没有收到plc允许下发工单请求")
            break
        result = grindPlc.get_allow_change_order()
        if not result.state:
            print(f"读取plc是否允许工单下发失败: {result.info}")
            break
        else:
            if result.data == 1:
                print("请求工单下发成功")

            else:
                print(f"res.data == {result.data}")
                gevent.sleep(0.5)
